package org.megha.blog.example.concurrency;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

/**
 * A mechanism to load test a given service by calling in multiple thread.
 * 
 * @author megha birmiwal
 */
public class LoadTester {
	
	/**
	 * Runs the given {@link Runnable} in a loop till a stop is requested.
	 */
	public static class LoopedLoadRunnerThread extends Thread {
		private Runnable load;
		private long count;
		boolean stopRequested = false;

		public LoopedLoadRunnerThread(Runnable load) {
			this.load = load;
		}
		
		@Override
		public void run() {
			while (!stopRequested) {
				load.run();
				count++;
				Thread.yield();
			}
		}
		
		public long getCount() {
			return count;
		}
		
		public void requestStop() {
			stopRequested = true;
			this.interrupt();
		}
	}
	
	private final Runnable load;
	private final int numThreads;
	Collection<LoopedLoadRunnerThread> threads = new ArrayList<LoopedLoadRunnerThread>();
	
	/** 
	 * Creates a LoadTester.
	 * 
	 * @param load the runnable to call in *each* thread repeatedly
	 * @param numThreads the number of threads
	 */
	public LoadTester(Runnable load, int numThreads) {
		this.load = load;
		this.numThreads = numThreads;
	}
	
	/**
	 * Starts the load test and waits for the user to press the enter key. 
	 * When the user presses the enter key, all load testing is stopped
	 * and the method returns. Before returning, the method prints the
	 * number of times the load was called.
	 **/
	public void loadTest() {
		for(int i = 0; i < numThreads; i++) {
			LoopedLoadRunnerThread thread = new LoopedLoadRunnerThread(load);
			thread.start();
			threads.add(thread);
		}
		
		System.out.println("Please enter any key to stop the load testing.");
		try {
			System.in.read();
		} catch (IOException e) {
			System.out.println("Exception occured while reading the input");
			e.printStackTrace();
		}
		
		for (LoopedLoadRunnerThread thread : threads) {
			System.out.println("terminating the thread");
			thread.requestStop();
		}
		
		long totalCount = 0; 
		for (LoopedLoadRunnerThread thread : threads) {
			try {
				thread.join();
			} catch (InterruptedException e) {
				System.out.println("The main thread is interrupted.");
				e.printStackTrace();
			}
			totalCount += thread.getCount();
		}
		System.out.println("Load called :" + totalCount + " times.");
	}

	public static void main(String args[]) {
		MyLoad load = new MyLoad();
		LoadTester tester = new LoadTester(load, 20);
		tester.loadTest();
		load.printCount();
	}

	public static class MyLoad implements Runnable {
		int count = 0;
		// the synchronized block can synchronize on 
		// this or lock, or any other shared object
		//Object lock = new Object();
		@Override
		public void run() {
// 			System.out.print('.');
			synchronized(this) {
				count++;
			}
//			try {
//				Thread.sleep(30000);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
		}
		
		void printCount() {
			System.out.println("load was executed " + count + " times");
		}
	}
}
